# -*- coding: iso-8859-15 -*-

import __builtin__
import os
import numpy as np

__all__ = ["open", "openfp", "Error"]

class Error(Exception):
  pass

# Determine endian-ness
import struct
if struct.pack("h", 1) == "\000\001":
    big_endian = 1
else:
  big_endian = 0

class ReadRPC(object):
  """
  Read RPC III files.
  A RPC III file is a data file conforming to the RPC III file specification
  developed by MTS corporation.  The data captured by the SoMat system can be
  saved in the RPC format.

  Hennie van Niekerk
  3/13/2009
  """

  def __init__(self, f):
    # The measurement rate is coded with values > 6000.
    # Second column of _rates contains sampling rate in Hz
    _rate = range(6300,6342)
    _rate.extend(range(6345,6348))
    _rate.extend(range(6350,6362))
    _freq = [1., 2., 5., 10., 25., 50., 60., 75., 100., 150., 200., 300., 400.,
	     600., 800., 1200., 1600., 2400.,3200., 4800., 9600., 3., 4., 6.
	     ,8., 15., 20., 30., 48., 64., 80., 96., 120., 160., 192., 240.,
	     320., 384., 480., 640., 960., 1920., 19200., 38400., 76800.,
	     1./2., 1./5., 1./10., 1./20., 1./50., 1./100., 1./200., 1./500.,
	     1./1.E3, 1./2.E3, 1./5.E3, 1./1.E4]
    self._freqs = {}
    for i in range(len(_rate)):
      self._freqs[_rate[i]] = _freq[i]

    self._fileinfo = {'FileID':None, 'ChannelC':None, 'ByteLine':None,
                      'MVLines':None, 'MWformat':None, 'MRate':None,
                      'DataOffset':None, 'TimeFormat':None, 'cmt':None,
                      'cmtpar':None, 'creation':None, 'usedslot':None,
                      'filename':'text', 'filesize':None}
                
    self._chaninfo = {'Channelnumber':None, 'Scaling':None, 'Offset':None,
                      'Unit':None, 'SigBit':None, 'Subchannel':None,
                      'data':None, 'timeax':None, 'ChannelName':None,
                      'chncode':None, 'SigName':None, 'SigTypus':None}

    self._chsetting = {'ChannelName':None, 'chncode':None, 'ampcode':None,
                       'paraflg':None, 'tabquelle':None, 'acalflg':None,
                       'bridget':None, 'bridgev':None, 'uk':None,
                       'frequenz':None}

    self._i_opened_the_file = None
    if isinstance(f, basestring):
      f = __builtin__.open(f, 'rb')
      self._i_opened_the_file = f
    # else, assume it is an open file object already
    try:
      f.seek(0,2); # go to end of file
      self._filesize = f.tell();
      f.seek(fid,0,0);
      self.initfp(f)

    except:
      if self._i_opened_the_file:
        f.close()
      raise

  def initfp(self, file):
    """
    Read the first8 long values in a CP42 measurement file (on PC).
    This contains the header block.
    Pos  Offset  Type  Content
    0      0     long  File-ID (6001)
    1      4     long  Channel Count
    2      8     long  Size of Measurement Line in Byte
    3     12     long  Number of Measurement Lines in file
    4     16     long  Measurement Value format
    5     20     long  Base Sample Rate (IDS Code e. g. 6317)
    6     24     long  Data Offset in Byte (Start of Meas.-Values in file)
    7     28     long  Time Channel Format (TCS command, only with CP42)
   """
    # Read Header Block. The header block of all CP42 measurement files (also
    # rainflow version CP42S30) is same: 8 long values
    _head = struct.unpack('<8l', file.read(32))

    if _head[0] != 6001:
      print "Invalid File identifier : %d" % _head[0]
      # close file and exit

    if _head[7] == 7001 or _head[7] == 7003:
      print "32 Bit MGC Time and USB Frame Count are not supported"
      print "Current Time Format Identifier : %d" % _head[7]
      # close file and exit
      raise
    # Only 4 Byte INT Intel format is supported. With newest versions of the
    # HBM setup assistant, only this format is upported.
    if _head[4] != 1253:
      print "Data not in 4 Byte INT (Intel) Format !"
      print "Current Format Identifier : %d" % _head[4]
      # close file and return
      raise

    if self._freqs.has_key(_head[5]):
      self._SR  = self._freqs[_head[5]]
    else:
      print "Unknown Measurement Rate : %d" % _head[5]
      raise

    self._no_of_samples = np.floor((self._filesize - _head[6]) ./ _head[2]);

    if _head[3] != self._no_of_samples:  # Sometimes some files do not have
      print "Information"                # info about no. of samples
      print "Number of Samples contained"
      print "in File seems to be invalid"
      print "File : %s" % _head[3]
      print "Calculated : %d" % self._no_of_samples
      _head[3] = self._no_of_samples

    self._fileinfo['FileID']= _head[0]       # File-ID (6001)
    self._fileinfo['ChannelC'] = _head[1]    # Channel Count (inc Time Channels)
    self._fileinfo['ByteLine'] = _head[2]    # Size MeasVal-Line / Bytes
    self._fileinfo['MVLines'] = _head[2]     # No. of MeasVal-Lines
    self._fileinfo['MWformat'] = _head[4]    # MeasVal-Format
    # (1253 is required: 4 Byte INT Intel)
    self._fileinfo['MRate'] = SR;            # Sample Rate in Hz
    self._fileinfo['DataOffset'] = _head[6]  # Data Offset in Byte
    self._fileinfo['TimeFormat'] = _head[7]  # Time Channel Format
    # (see TCS command, only with CP42)
    self._fileinfo['filename'] = meafile;
    self._fileinfo['filesize'] = filesize;


    self._getChHeaders()
    self._getHeaders()

def _channel_parms(self):
  """
  Read Block with channel parameters
  """
  
  for ch in range(self._fileinfo['ChannelC']):
    self._chaninfo['Channelnumber'] = fread(fid,1,'long');     % Channelnumber
    self._chaninfo['Scaling'] = fread(fid,1,'float');          % Scale Factor
    self._chaninfo['Offset'] = fread(fid,1,'float');           % Offset
    self._chaninfo['Unit'] = setstr(fread(fid,[1,4],'char'));  % Unit
    self._chaninfo['SigBit'] = fread(fid,1,'short');           % Signal-Bitfeld
    
    sigbin=flipud(dec2bin(chaninfo.SigBit));        % Convert Signal Bitfield
                                                    % to binary representation
    if length(sigbin)>6,                            % Only the lower 6 Bit
        sigbin=sigbin(1:6);                         % contain sig. types!
    end
    numsigs=sum(sigbin);                            % No. of Signals / Channels
    kitypus=find(sigbin==1);                        % Positions of signals
    
    sucha=fread(fid,1,'short');                     % Subchannel No.
    if sucha==0,
        chaninfo.Subchannel=1;
    else
        chaninfo.Subchannel=sucha;
    end
    
    for sgtype=1:numsigs,                           % Evaluate Signal-
        typaktuell=kitypus(sgtype);                 % type information
        switch typaktuell
            case {1}
                chaninfo.SigName=' (Gross)';
            case {2}
                chaninfo.SigName=' (Net)';
            case {3}
                chaninfo.SigName=' (PV1)';
            case {4}
                chaninfo.SigName=' (PV2)';
            case {5}
                chaninfo.SigName=' (CPV)';
            case {6}
                chaninfo.SigName=' (DIG)';
        end
        chaninfo.SigTypus=typaktuell;
        CHINFO=[CHINFO,chaninfo];
    end
end




  def header(self):
    """
    Return a dictionary containing the file header data that do
    not pertain to the channels
    """
    return self._Header

  def chheader(self):
    """
    Return a dictionary containing the file header data that is
    channel specific.
    """
    return self._chHeader

  def _getHeader(self, name, RPCname, dtype = 'text'):
    """
    Read a record with non-channel specific data from the header section of a
    RPC III file.
    The data has already been read from the file and placed in a dictionary.
    The recovered data is placed in a dictionary. The value of each header
    record which is stored as text in the RPC III file is converted to
    integer, double, or kept as text.
    Input agruments:
      name: The Key name for the output dictionary
      RPCname: The Key name for the input dictionary
      dtype:  The data type of the value, 'int', 'float' or 'text'
    """
    try:
      if dtype == 'int':
        self._Header[name] = int(self._fHeader[RPCname])
      elif dtype == 'float':
        self._Header[name] = float(self._fHeader[RPCname])
      else:
        self._Header[name] = self._fHeader[RPCname]
      del self._fHeader[RPCname]
    except KeyError:
      print 'KeyError',name
      pass
    except:
      print 'Oops, error occured :',  RPCname, self._fHeader[RPCname]

  def _getHeaders(self):
    """
    Get all the non-channel specific headers and place in a dictionary which
    are accesable to the user of the class.
    """
    self._Header = {}
    self._Headers = {'BYPASS_FILTER':None,'CHANNELS':None,'DATA_TYPE':None,
                    'TIME':None, 'DELTA_T':None, 'FILE_TYPE':None,
                    'FORMAT':None, 'FRAMES':None, 'HALF_FRAMES':None,
                    'INT_FULL_SCALE':None, 'NUM_HEADER_BLOCKS':None,
                    'NUM_PARAMS':None, 'OPERATION':None, 'PARENT_1':None,
                    'PARENT_2':None, 'PARENT_3':None, 'PARENT_4':None,
                    'PARENT_4':None,'PARTITIONS':None, 'PTS_PER_FRAME':None,
                    'PTS_PER_GROUP':None, 'REPEATS':None, 'TIME_TYPE':None}

    self._HeadersFormat = {'BYPASS_FILTER':'int','CHANNELS':'int',
                           'DATA_TYPE':'text','TIME':'text','DELTA_T':'float',
                           'FILE_TYPE':'text','FORMAT':'text','FRAMES':'int',
                           'HALF_FRAMES':'int','INT_FULL_SCALE':int,
                           'NUM_HEADER_BLOCKS':'int','NUM_PARAMS':'int',
                           'OPERATION':'text','PARENT_1':'text',
                           'PARENT_2':'text','PARENT_3':'text',
                           'PARENT_4':'text','PARTITIONS':'int',
                           'PTS_PER_FRAME':'int','PTS_PER_GROUP':'int',
                           'REPEATS':'int','TIME_TYPE':'text'}

    for k in self._Headers:
      self._getHeader(k, k, self._HeadersFormat[k] )




############# Other headers end

  def _getChHeader(self, name, RPCname, Channel, dtype = 'text'):
    """
    Read a record with channel specific from the header section of a RPC III
    file. The data has already been read from the file and placed in a
    dictionary, self._fHeader.  The recovered data is placed in another
    dictionary, self._chHeader, which contains nested dictionaries for each
    channel measured. The value of each header record which is stored as
    text in the RPC III file is converted to integer, double, or kept as text.
    Input agruments:
      name: The Key name for the output dictionary
      RPCname: The Key name for the input dictionary
      Channel: The input channel number for which header data is returned
      dtype:  The data type of the value, 'int', 'float' or 'text'
    """
    try:
      Ch = 'ch' + repr(Channel).zfill(3)
      if dtype == 'int':
        self._chHeader[Ch][name] = int(self._fHeader[RPCname + '_' + 
                                                     repr(Channel)])
      elif dtype == 'float':
        self._chHeader[Ch][name] = float(self._fHeader[RPCname + '_' + 
                                                       repr(Channel)])
      else:
        self._chHeader[Ch][name] = self._fHeader[RPCname + '_' + 
                                                 repr(Channel)]
      del self._fHeader[RPCname + '_' + repr(Channel)]
    except KeyError:
      pass
    except:
      print 'Oops, error occured :', Ch, name, (RPCname + '_' + repr(Channel))

  def _getChHeaders(self):
    """
    Get all the channel specific headers and place in a dictionary which are
    accessible to the user of the class.
    """
    try:
      for i in range(int(self._fHeader['CHANNELS'])):
        Ch =  'ch' + repr(i+1).zfill(3)
        self._chHeader[Ch] = {}
        self._getChHeader('Description', 'DESC.CHAN', i+1)
        self._getChHeader('LowerLimit','LOWER_LIMIT.CHAN',  i+1, 'int')
        self._getChHeader('MappedTo', 'MAP.CHAN',  i+1, 'int')
        self._getChHeader('NumberInPartition', 'PART.NCHAN',  i+1, 'int')
        self._getChHeader('Scale', 'SCALE.CHAN',  i+1, 'float')
        self._getChHeader('Units', 'UNITS.CHAN',  i+1)
        self._getChHeader('UpperLimit', 'UPPER_LIMIT.CHAN',  i+1, 'float')
    except KeyError:
      print 'Number of channels must be specified in the file'

  def close(self):
    if self._i_opened_the_file:
      self._i_opened_the_file.close()
      self._i_opened_the_file = None
    self._file = None

def open(f, mode=None):
    if mode is None:
        if hasattr(f, 'mode'):
            mode = f.mode
        else:
            mode = 'rb'
    if mode in ('r', 'rb'):
        return ReadRPC(f)
    elif mode in ('w', 'wb'):
        return WriteRPC(f)
    else:
        raise Error, "mode must be 'r', 'rb', 'w', or 'wb'"


if __name__ == "__main__":
  ifile = 'Data1.mea'
  fp = open(ifile,'rb')
  heads = fp.header()
  chHeads = fp.chheader()
  fp.close()
